/*
 * Copyright 2016-2019 yoara
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package yhao.infra.feature.solr.local.core;

import org.apache.log4j.Logger;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import yhao.infra.common.model.Pagination;
import yhao.infra.feature.solr.local.AbstractSearcher;
import yhao.infra.feature.solr.local.SearchCriteria;
import yhao.infra.feature.solr.local.Searcher;
import yhao.infra.feature.solr.local.SolrUpdateParam;
import yhao.infra.feature.solr.local.core.facet.FacetCallbackHandler;
import yhao.infra.feature.solr.local.core.facet.FacetSolrPagination;
import yhao.infra.feature.solr.local.core.group.GroupCallbackHandler;
import yhao.infra.feature.solr.local.core.group.GroupSolrPagination;
import yhao.infra.feature.solr.local.core.stats.StatsCallbackHandler;
import yhao.infra.feature.solr.local.core.stats.StatsSolrPagination;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Solr操作模板类
 *
 * @see AbstractSearcher
 * @author yoara
 */
public class SolrTemplate<T> {
	private final static Logger logger = Logger.getLogger(SolrTemplate.class);
	private SolrClient solrClient;
	private Searcher<T> searcher;
	public SolrTemplate(AbstractSearcher<T> searcher) {
		this.searcher = searcher;
		this.solrClient = searcher.getSolrServer();
	}

	public Object execute(SolrCallback solrCallback) {
		try {
			Object result = solrCallback.doInSolr(solrClient);
			return result;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 根据SolrQuery查询
	 * @param criteria
	 * @param pagination
	 * @return
	 */
	public Pagination<T> query(SearchCriteria criteria, Pagination<T> pagination) {
		return (Pagination<T>) execute(new SolrCallbackHandler<>(this, criteria, pagination));
	}

	public Pagination<T> queryGroup(SearchCriteria criteria, GroupSolrPagination<T> pagination) {
		return (Pagination<T>) execute(new GroupCallbackHandler<>(this, criteria, pagination));
	}

    public Pagination<T> queryFacet(SearchCriteria criteria, FacetSolrPagination<T> pagination) {
	    return (Pagination<T>) execute(new FacetCallbackHandler<>(this, criteria, pagination));
	}
	public Pagination<T> queryStats(SearchCriteria criteria, StatsSolrPagination<T> pagination) {
		return (Pagination<T>) execute(new StatsCallbackHandler<>(this, criteria, pagination));
	}

	public QueryResponse queryResponse(SearchCriteria criteria) throws IOException, SolrServerException {
		SolrQuery solrQuery = criteria.makeSolrQuery();
		return solrClient.query(solrQuery, SolrRequest.METHOD.POST);
	}

	public QueryResponse queryResponseCustomer(SolrQuery solrQuery) throws IOException, SolrServerException {
		return solrClient.query(solrQuery, SolrRequest.METHOD.POST);
	}

	public List<T> solrDocumentsToEntities(SolrDocumentList solrDocumentList) throws Exception {
		List<T> entities = new ArrayList<>();
		if (null != solrDocumentList) {
			for(SolrDocument solrDocument : solrDocumentList) {
				entities.add(searcher.solrDocumentToEntity(solrDocument));
			}
		}
		return entities;
	}

	/**
	 * 提交数据
	 */
	public void save(final T entity) {
		save(entity,null);
	}

	/**
	 * 提交数据
	 */
	public void save(final T entity,CommitParameter param) {
		saveList(new ArrayList<>() {{
			add(entity);
		}},param);
	}

	/**
	 * 批量保存
	 * @param entities
	 */
	public void saveList(final List<T> entities) {
		saveList(entities,null);
	}

	/**
	 * 批量保存
	 * @param entities
	 */
	public void saveList(final List<T> entities,CommitParameter parameter) {
		if(parameter==null){
			parameter = new CommitParameter();
		}
		final CommitParameter param = parameter;
		execute(solrServer -> {
			solrServer.addBeans(entities);
			if(param.isNeedCommit()){
				solrServer.commit(param.isWaitFlush(), param.isWaitSearcher(), param.isSoftCommit());
			}
			return null;
		});
	}

	/**
	 * 更新数据
	 */
	public void update(SolrUpdateParam updateParam) {
		update(updateParam,null);
	}

	/**
	 * 更新数据
	 */
	public void update(SolrUpdateParam updateParam,CommitParameter parameter) {
		if(updateParam==null||updateParam.getParamList().size()==0){
			return;
		}
		final SolrInputDocument doc = new SolrInputDocument();
		doc.addField(updateParam.getIdName(),updateParam.getIdValue());
		for(SolrUpdateParam.UpdateParam param:updateParam.getParamList()){
			doc.addField(param.getField(),param.pickUpdateInfo());
		}
		if(parameter==null){
			parameter = new CommitParameter();
		}
		final CommitParameter param = parameter;
		execute(solrServer -> {
			solrServer.add(doc);
			if(param.isNeedCommit()){
				solrServer.commit(param.isWaitFlush(), param.isWaitSearcher(), param.isSoftCommit());
			}
			return null;
		});
	}

	/**
	 * 根据ID删除索引
	 * @param id
	 */
	public void deleteById(final String id) {
		deleteById(id,null);
	}

	/**
	 * 根据ID删除索引
	 * @param id
	 */
	public void deleteById(final String id,CommitParameter parameter) {
		deleteByIds(new ArrayList<>() {{
			add(id);
		}},parameter);
	}

	/**
	 * 根据ID批量删除索引
	 */
	public void deleteByIds(final List<String> ids) {
		deleteByIds(ids,null);
	}

	/**
	 * 根据ID批量删除索引
	 */
	public void deleteByIds(final List<String> ids,CommitParameter parameter) {
		if(parameter==null){
			parameter = new CommitParameter();
		}
		final CommitParameter param = parameter;
		execute(solrServer -> {
			solrServer.deleteById(ids);
			if(param.isNeedCommit()){
				solrServer.commit(param.isWaitFlush(), param.isWaitSearcher(),param.isSoftCommit());
			}
			return null;
		});
	}

	/**
	 * 根据query批量删除索引
	 */
	public void deleteByQuery(final String query) {
		deleteByQuery(query,null);
	}

	/**
	 * 根据query批量删除索引
	 */
	public void deleteByQuery(final String query,CommitParameter parameter) {
		if(parameter==null){
			parameter = new CommitParameter();
		}
		final CommitParameter param = parameter;
		execute(solrServer -> {
			solrServer.deleteByQuery(query);
			if(param.isNeedCommit()){
				solrServer.commit(param.isWaitFlush(), param.isWaitSearcher(),param.isSoftCommit());
			}
			return null;
		});
	}

	/**
	 * 删除所有元素
	 */
	public void deleteAll() {
		deleteAll(null);
	}

	/**
	 * 删除所有元素
	 */
	public void deleteAll(CommitParameter parameter) {
		deleteByQuery("*:*",parameter);
	}
	
	/**
	 * 优化solr数据存储结构
	 * 
	 */
	public void optimize(final int maxSegments) {
		execute(solrServer -> {
            logger.info("Optimize solr start ...");
            long now = System.currentTimeMillis();
            solrServer.optimize(false, false, maxSegments);
            logger.info("Optimize solr end, time = " + (System.currentTimeMillis() - now));
            return null;
        });
	}

	static public class CommitParameter{
		private boolean needCommit = true;
		private boolean waitFlush;
		private boolean waitSearcher;
		private boolean softCommit = true;

		public boolean isWaitFlush() {
			return waitFlush;
		}

		public void setWaitFlush(boolean waitFlush) {
			this.waitFlush = waitFlush;
		}

		public boolean isWaitSearcher() {
			return waitSearcher;
		}

		public void setWaitSearcher(boolean waitSearcher) {
			this.waitSearcher = waitSearcher;
		}

		public boolean isSoftCommit() {
			return softCommit;
		}

		public void setSoftCommit(boolean softCommit) {
			this.softCommit = softCommit;
		}

		public boolean isNeedCommit() {
			return needCommit;
		}

		public void setNeedCommit(boolean needCommit) {
			this.needCommit = needCommit;
		}
	}
}
